import filecmp
import sys
import os

import table_ast
import table_lexer
import table_parser

class GoTableConfig:
    def __init__(self, pkgName, keyType, keyValue):
        self.pkgName = pkgName
        self.keyType, self.keyValue = keyType, keyValue

def to_go_files(indir, outdir, cfg):
    def check_replace_file(filename):
        os.system('gofmt -w %s' % filename)
        if not os.path.exists(filename[:-1]) or \
           not filecmp.cmp(filename, filename[:-1], False):
            os.replace(filename, filename[:-1])
        else:
            os.remove(filename)

    def parse_ast(infile):
        lexer = table_lexer.TableLexer()
        lexer.parse(infile)
        parser = table_parser.TableParser()
        return parser.set_parent_entity_node(parser.parse(lexer))

    def parse_blanks(infile):
        blanks = []
        with open(infile, 'r') as fo:
            for i, line in enumerate(fo.readlines()):
                if line.isspace():
                    blanks.append(i + 1)
        return blanks

    def generate(indir, outdir, filename):
        filepart = os.path.splitext(filename)[0]
        outpathpart = os.path.join(outdir, filepart)
        infile = os.path.abspath(os.path.join(indir, filename))
        print('go files %s ...' % infile)
        root, blanks = parse_ast(infile), parse_blanks(infile)
        outfile, prefix = outpathpart+'.go~', \
            'package %s\n\n' % cfg.pkgName
        GeneratorGO(blanks, cfg).Generate(root, outfile, prefix)
        check_replace_file(outfile)

    if os.path.exists(indir):
        if not os.path.exists(outdir):
            os.mkdir(outdir)
        for filename in os.listdir(indir):
            generate(indir, outdir, filename)

class GeneratorBase:
    @staticmethod
    def _to_go_tag_type(node):
        if node.container is None:
            if isinstance(node.parts[0], table_ast.DateTime):
                return 'datetime'

    @classmethod
    def _to_go_type(cls, entity, node):
        if node.container is None:
            if isinstance(node.parts[0], table_ast.NsIdList):
                return cls._to_go_ns(node.parts[0],
                    cls._get_go_prefix_type(entity, node.parts[0]))
            if isinstance(node.parts[0], table_ast.DateTime):
                return cls.__to_go_datetime(node.parts[0])
            return cls.__to_go_base_type(node.parts[0].value)
        if node.container == 'Sequence':
            return '[]' + cls._to_go_type(entity, node.parts[0])
        if node.container == 'Associative':
            return 'map[' + \
                cls._to_go_type(entity, node.parts[0]) + ']' + \
                cls._to_go_type(entity, node.parts[1])
        if node.container == 'Unique':
            return '[]' + cls._to_go_type(entity, node.parts[0])

    @classmethod
    def _to_go_ns(cls, node, idList):
        idList = idList.copy() if idList else []
        idList.extend(node.idList)
        return '_'.join(cls._to_go_name(ns.value) for ns in idList)

    @staticmethod
    def _to_go_name(name):
        return name[0].upper() + name[1:]

    @staticmethod
    def __to_go_datetime(node):
        return 'int64'

    @staticmethod
    def __to_go_base_type(strType):
        allTypes = {'float': 'float32', 'double': 'float64',
                    'bytes': '[]byte'}
        if strType in allTypes:
            return allTypes[strType]
        else:
            return strType

    @classmethod
    def _get_go_prefix_type(cls, entity, node, isFound = False):
        if not isFound:
            entity, name = entity.parent, node.idList[0].value
            while not isFound and \
                table_parser.TableParser.is_definition_entity_node(entity):
                for member in entity.declarationList.declarationList:
                    if table_parser.TableParser.is_definition_entity_node(member):
                        if member.name.value == name:
                            isFound = True
                            break
                else:
                    entity = entity.parent
        if isFound:
            idList = []
            while table_parser.TableParser.is_definition_entity_node(entity):
                idList.append(entity.name)
                entity = entity.parent
            idList.reverse()
            return idList

class GeneratorGO(GeneratorBase):
    def __init__(self, blanks, cfg):
        self.isFirstBlockMember = False
        self.blanks, self.i = blanks, 0
        self.stki, self.lineno = [], 0
        self.cfg = cfg

    def Generate(self, root, outfile, prefix = ''):
        with open(outfile, 'w') as self.fo:
            self.fo.write(prefix)
            for entity in root.externalDeclarations:
                self.__generateentityall(entity, '')

    def __generateentityall(self, entity, prefix):
        self.__generateentitytraverse(entity, prefix)
        self.__generateentity(entity, prefix)

    def __generateentitytraverse(self, entity, prefix):
        if table_parser.TableParser.is_definition_entity_node(entity):
            self.__generateblockmembertraverse(entity,
                self.__to_go_full_name(prefix, entity.name.value))

    def __generateblockmembertraverse(self, entity, prefix):
        for member in entity.declarationList.declarationList:
            if table_parser.TableParser.is_definition_entity_node(member):
                self.__generateentityall(member, prefix)

    def __generateentity(self, entity, prefix):
        if isinstance(entity, table_ast.TableDefinition):
            return self.__generatetableblock(entity, prefix)
        if isinstance(entity, table_ast.StructDefinition):
            return self.__generatestructblock(entity, prefix)
        if isinstance(entity, table_ast.EnumDefinition):
            return self.__generateenumblock(entity, prefix)

        if isinstance(entity, table_ast.TableMemberVarDeclaration) or \
           isinstance(entity, table_ast.StructMemberVarDeclaration):
            return self.__generatestructmember(entity)
        if isinstance(entity, table_ast.EnumDeclaration):
            return self.__generateenummember(entity, prefix)

        if isinstance(entity, table_ast.Preprocessor):
            return self.__writeline2file(1 if prefix else 0,
                '//' + entity.text.value.rstrip(), entity.text.lineno)

        if isinstance(entity, table_ast.Comment) and \
           not self.__isCommentBelongBlockEntity(entity):
            return self.__writeline2file(1 if prefix else 0,
                entity.text.value.rstrip(), entity.text.lineno,
                canInline = True, strSpacer = '  ')

    def __generatetableblock(self, entity, prefix):
        fullName = self.__to_go_full_name(prefix, entity.name.value)
        self.__beginWriteBlockEntity(entity)
        self.__writeline2file(0, 'type ' + fullName + ' struct',
            entity.name.lineno)
        self.__generateblockmember(entity, fullName, '{}')
        self.__endWriteBlockEntity(entity)

    def __generatestructblock(self, entity, prefix):
        fullName = self.__to_go_full_name(prefix, entity.name.value)
        self.__beginWriteBlockEntity(entity)
        self.__writeline2file(0, 'type ' + fullName + ' struct',
            entity.name.lineno)
        inheritBase, extData = entity.inheritBase, None
        if inheritBase:
            def extData():
                self.__writeline2file(1, self._to_go_ns(inheritBase,
                    self._get_go_prefix_type(entity, inheritBase)))
                self.fo.write('\n')
        self.__generateblockmember(entity, fullName, '{}', extData)
        self.__endWriteBlockEntity(entity)

    def __generateenumblock(self, entity, prefix):
        fullName = self.__to_go_full_name(prefix, entity.name.value)
        self.idList, self.valList = [], []
        self.nsVal, self.iVal = '', -1
        self.wpos = None
        self.__beginWriteBlockEntity(entity)
        self.__writeline2file(0, '// %s\nconst' % fullName, entity.name.lineno)
        self.__generateblockmember(entity, fullName, '()')
        self.__endWriteBlockEntity(entity)

    def __generateblockmember(self, entity, prefix, brackets, extData = None):
        self.__writeline2file(0, brackets[0],
            entity.name.lineno, canInline = True, strSpacer = ' ')
        if extData: extData()
        self.isFirstBlockMember = True
        for member in entity.declarationList.declarationList:
            if not table_parser.TableParser.is_definition_entity_node(member):
                self.__generateentity(member, prefix)
        self.__writeline2file(0, brackets[1])
        if isinstance(entity, table_ast.TableDefinition):
            self.__generatetablefuncs(entity)

    def __generatestructmember(self, member):
        tag = ' rule:"%s"' % member.memRule.value \
            if hasattr(member, 'memRule') else ''
        tagType = self._to_go_tag_type(member.memType)
        if tagType:
            tag += ' type:"%s"' % tagType
        for var in member.memVarList.varDeclaratorList:
            self.__writeline2file(1, '%s %s `json:"%s,omitempty"%s`' %
                (self._to_go_name(var.value),
                 self._to_go_type(member, member.memType),
                 var.value, tag),
                var.lineno)

    def __generateenummember(self, member, prefix):
        fullName = self.__to_go_full_name(prefix, member.memName.value)
        self.__writeenummember(fullName, self.__enumValue2formatStr(
            member.parent, member.memValue), member.memName.lineno)

    def __writeenummember(self, idStr, valStr, lineno):
        if self.lineno != lineno:
            self.wpos = self.fo.tell()
            del self.idList[:], self.valList[:]
        self.fo.seek(self.wpos)
        self.idList.append(idStr), self.valList.append(valStr)
        self.__writeline2file(1,
            '%s = %s' % (', '.join(self.idList), ', '.join(self.valList)),
            lineno)

    def __writeline2file(self, level, linedata, lineno = -1, **kwargs):
        isWriteBlank = False
        while len(self.blanks) > self.i and self.blanks[self.i] < lineno:
            del self.blanks[self.i]
            isWriteBlank = True
        if self.isFirstBlockMember:
            self.isFirstBlockMember = False
        elif isWriteBlank:
            self.fo.write('\n')
        if kwargs and kwargs['canInline'] and self.lineno == lineno:
            self.fo.seek(self.fo.tell() - len(os.linesep))
            self.fo.write(kwargs['strSpacer'])
        elif level != 0:
            self.fo.write('\t' * level)
        self.fo.write(linedata)
        self.fo.write('\n')
        if lineno != -1:
            self.lineno = lineno

    def __enumValue2formatStr(self, entity, node):
        if isinstance(node, table_ast.NsIdList):
            self.nsVal, self.iVal = self.__to_go_enum_value(entity, node), 0
        elif node:
            self.nsVal, self.iVal = '', int(node)
        else:
            self.iVal += 1
        if self.nsVal and self.iVal != 0:
            return '%s+%d' % (self.nsVal, self.iVal)
        elif self.nsVal:
            return self.nsVal
        else:
            return str(self.iVal)

    def __findPrevBlankByLineno(self, lineno):
        for i in range(len(self.blanks)-1, -1, -1):
            if self.blanks[i] < lineno:
                return i

    def __findNextBlankByLineno(self, lineno):
        for i in range(len(self.blanks)):
            if self.blanks[i] > lineno:
                return i

    def __isCommentBelongBlockEntity(self, comment):
        i = self.__findNextBlankByLineno(comment.text.lineno)
        nextBlankLineno = self.blanks[i] if i is not None else sys.maxsize
        declarationList = self.__getParentNodeEntityChildrenList(comment)
        for entity in declarationList[declarationList.index(comment) + 1:]:
            if isinstance(entity, table_ast.Comment):
                if entity.text.lineno < nextBlankLineno:
                    continue
                return False
            if table_parser.TableParser.is_definition_entity_node(entity):
                return True
            return False

    def __tryWriteBlockEntityComments(self, entity):
        i = self.__findPrevBlankByLineno(entity.name.lineno)
        prevBlankLineno = self.blanks[i] if i is not None else -1
        declarationList = self.__getParentNodeEntityChildrenList(entity)
        stopIndex = declarationList.index(entity)
        for startIndex in range(stopIndex-1, -1, -1):
            if isinstance(declarationList[startIndex], table_ast.Comment):
                if declarationList[startIndex].text.lineno > prevBlankLineno:
                    continue
            startIndex += 1
            break
        else:
            startIndex = 0
        self.fo.write('\n')
        for i in range(startIndex, stopIndex):
            self.__writeline2file(0, declarationList[i].text.value.rstrip())

    def __beginWriteBlockEntity(self, entity):
        self.stki.append(self.i)
        self.i = self.__findNextBlankByLineno(entity.name.lineno)
        if self.i is None:
            self.i = sys.maxsize
        self.__tryWriteBlockEntityComments(entity)

    def __endWriteBlockEntity(self, entity):
        self.i = self.stki[-1]
        del self.stki[-1]

    @classmethod
    def __to_go_full_name(cls, prefix, name):
        return prefix + ('_' if prefix else '') + cls._to_go_name(name)

    @classmethod
    def __to_go_enum_value(cls, entity, node):
        return cls._to_go_ns(node,
            cls._get_go_prefix_type(entity, node, len(node.idList) == 1))

    @staticmethod
    def __getParentNodeEntityChildrenList(node):
        if isinstance(node.parent, table_ast.TranslationUnit):
            return node.parent.externalDeclarations
        elif table_parser.TableParser.is_definition_entity_node(node.parent):
            return node.parent.declarationList.declarationList

    def __generatetablefuncs(self, entity):
        name, keyName, tblName = self._to_go_name(entity.name.value), None, None
        if entity.metaInfoList:
            for metaInfo in entity.metaInfoList.metaInfoList:
                if metaInfo[0].value == 'key':
                    keyName = metaInfo[1].value
                elif metaInfo[0].value == 'tblname':
                    tblName = metaInfo[1].value

        self.fo.write('\n')

        self.__writeline2file(0, 'func (*%s) GetTableName() string {' % name)
        self.__writeline2file(1, 'return "%s"' % (tblName or entity.name.value))
        self.__writeline2file(0, '}')

        self.__writeline2file(0, 'func (*%s) GetTableKeyName() string {' % name)
        self.__writeline2file(1, 'return "%s"' % (keyName or ''))
        self.__writeline2file(0, '}')

        self.__writeline2file(0, 'func (obj *%s) GetTableKeyValue() %s {' % (name, self.cfg.keyType))
        self.__writeline2file(1, 'return %s' %
            ('%s(obj.%s)' % (self.cfg.keyType, self._to_go_name(keyName)) if keyName else self.cfg.keyValue))
        self.__writeline2file(0, '}')
